package cn.iocoder.yudao.module.system.service.mail;

import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.lang.Assert;
import cn.iocoder.yudao.module.system.api.mail.dto.code.MailCodeSendReqDTO;
import cn.iocoder.yudao.module.system.api.mail.dto.code.MailCodeUseReqDTO;
import cn.iocoder.yudao.module.system.api.mail.dto.code.MailCodeValidateReqDTO;
import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailCodeDO;
import cn.iocoder.yudao.module.system.dal.mysql.mail.MailCodeMapper;
import cn.iocoder.yudao.module.system.enums.mail.MailSceneEnum;
import cn.iocoder.yudao.module.system.framework.mail.config.MailCodeProperties;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;

import jakarta.annotation.Resource;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;

import static cn.hutool.core.util.RandomUtil.randomInt;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.isToday;
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;

/**
 * 邮箱验证码 Service 实现类
 *
 * @author miaoqw
 */
@Service
@Validated
public class MailCodeServiceImpl implements MailCodeService {

    @Resource
    private MailCodeProperties mailCodeProperties;

    @Resource
    private MailCodeMapper mailCodeMapper;

    @Resource
    private MailSendService mailSendService;

    @Override
    public void sendMailCode(MailCodeSendReqDTO reqDTO) {
        MailSceneEnum sceneEnum = MailSceneEnum.getCodeByScene(reqDTO.getScene());
        Assert.notNull(sceneEnum, "验证码场景({}) 查找不到配置", reqDTO.getScene());
        // 创建验证码
        String code = createMailCode(reqDTO.getEmail(), reqDTO.getScene(), reqDTO.getCreateIp());
        // 发送验证码
        final Map<String, Object> templateParams = new HashMap<>();
        templateParams.put("code", code);
        // 遍历code
        for (int i = 0; i < code.length(); i++) {
            final char character = code.charAt(i);
            final String letter = String.valueOf(character);
            templateParams.put("code" + (i + 1), letter);
        }
        mailSendService.sendSingleMail(reqDTO.getEmail(), null, null,
                sceneEnum.getTemplateCode(), templateParams);
    }

    private String createMailCode(String email, Integer scene, String ip) {
        // 校验是否可以发送验证码，不用筛选场景
        MailCodeDO lastMailCode = mailCodeMapper.selectLastByMail(email, null, null);
        if (lastMailCode != null) {
            if (LocalDateTimeUtil.between(lastMailCode.getCreateTime(), LocalDateTime.now()).toMillis()
                    < mailCodeProperties.getSendFrequency().toMillis()) { // 发送过于频繁
                throw exception(MAIL_CODE_SEND_TOO_FAST);
            }
            if (isToday(lastMailCode.getCreateTime()) && // 必须是今天，才能计算超过当天的上限
                    lastMailCode.getTodayIndex() >= mailCodeProperties.getSendMaximumQuantityPerDay()) { // 超过当天发送的上限。
                throw exception(MAIL_CODE_EXCEED_SEND_MAXIMUM_QUANTITY_PER_DAY);
            }
            // TODO 芋艿：提升，每个 IP 每天可发送数量
            // TODO 芋艿：提升，每个 IP 每小时可发送数量
        }

        // 创建验证码记录
        String code = String.valueOf(randomInt(mailCodeProperties.getBeginCode(), mailCodeProperties.getEndCode()));
        MailCodeDO newSmsCode = MailCodeDO.builder().email(email).code(code).scene(scene)
                .todayIndex(lastMailCode != null && isToday(lastMailCode.getCreateTime()) ? lastMailCode.getTodayIndex() + 1 : 1)
                .createIp(ip).used(false).build();
        mailCodeMapper.insert(newSmsCode);
        return code;
    }

    @Override
    public void useMailCode(MailCodeUseReqDTO reqDTO) {
        // 检测验证码是否有效
        MailCodeDO lastMailCode = validateMailCode0(reqDTO.getEmail(), reqDTO.getCode(), reqDTO.getScene());
        // 使用验证码
        mailCodeMapper.updateById(MailCodeDO.builder().id(lastMailCode.getId())
                .used(true).usedTime(LocalDateTime.now()).usedIp(reqDTO.getUsedIp()).build());
    }

    @Override
    public void validateMailCode(MailCodeValidateReqDTO reqDTO) {
        validateMailCode0(reqDTO.getEmail(), reqDTO.getCode(), reqDTO.getScene());
    }

    private MailCodeDO validateMailCode0(String email, String code, Integer scene) {
        // 校验验证码
        MailCodeDO lastSmsCode = mailCodeMapper.selectLastByMail(email, code, scene);
        // 若验证码不存在，抛出异常
        if (lastSmsCode == null) {
            throw exception(MAIL_CODE_NOT_FOUND);
        }
        // 超过时间
        if (LocalDateTimeUtil.between(lastSmsCode.getCreateTime(), LocalDateTime.now()).toMillis()
                >= mailCodeProperties.getExpireTimes().toMillis()) { // 验证码已过期
            throw exception(MAIL_CODE_EXPIRED);
        }
        // 判断验证码是否已被使用
        if (Boolean.TRUE.equals(lastSmsCode.getUsed())) {
            throw exception(MAIL_CODE_USED);
        }
        return lastSmsCode;
    }

}
